﻿using Nemerle.Collections;
using Nemerle.Text;
using Nemerle.Utility;
using Nemerle;
using Nemerle.Imperative;

using System;
using System.Collections.Generic;
using System.Linq;
using SR = System.Reflection;

namespace Nemerle2.Compiler
{
  /// <summary>
  /// Абстрактный базовый класс описывающий информацию о типах для конкретного типа.
  /// Можно сказать, что это описание типа без его аргументов типов. Таким образом NTypeInfo
  /// не может описывать так называемый воплощенный тип (instantiated type), но является базой
  /// для их описания. Воплощенные типы описываются с помощью вариантного типа FixedType.
  /// </summary>
  public abstract class NTypeInfo : NMemberInfo, ITypeInfo, IInfo
  {
    // Properties

    public abstract Assembly : IReferencedAssembly { get }

    public abstract Extends        : FixedType.Class { get }
    public abstract Implements     : Seq[FixedType.Class] { get }
    public abstract TypeParams     : Seq[ITypeParameterInfo] { get }
    
    public abstract Namespace      : list[string] { get; }

    public abstract Accessibility       : Accessibility { get; }
    public abstract AttributeTargets    : AttributeTargets { get; }
    public abstract DefaultIndexerName  : string { get; }

    public sealed override MemberKind : MemberKind { get { MemberKind.TypeInfo } }
    public sealed override IsPublic   : bool       { get { this.Accessibility == Accessibility.Public; } }
    public sealed override IsPrivate  : bool       { get { this.Accessibility == Accessibility.Private; } }
    public sealed override IsInternal : bool       { get { this.Accessibility == Accessibility.Internal; } }

    public sealed override IsProtected : bool
    {
      get
      {
        match (this.Accessibility)
        {
          | Protected | ProtectedAndInternal | ProtectedOrInternal => true
          | _ => false
        }
      }
    }

    public FrameworkTypeName : string
    {
      get
      {
         if (DeclaringType == null)
           FullName
         else
            // TODO: Добавить информацию о количестве параметров типов.
           DeclaringType.FrameworkTypeName + "+" + Name
      }
    }

    public abstract FullName   : string { get; }
    public abstract IsDelegate : bool   { get; }
    public abstract IsEnum     : bool   { get; }

    
    public IsExternallyAccessible(isFriend : bool) : bool
    {      
      def declTypeAccessibility() : bool
      {
        if (DeclaringType == null) true
        else DeclaringType.IsExternallyAccessible(isFriend);
      }
      match(this.Accessibility)
      {
        | Private => false;
        | Public | Protected | ProtectedOrInternal => declTypeAccessibility();
        | Internal | ProtectedAndInternal =>  declTypeAccessibility() && isFriend;
      }
    }

    public abstract IsFloating            : bool                   { get; }
    public abstract IsInterface           : bool                   { get; }
    public abstract IsModule              : bool                   { get; }
    public abstract IsNumeric             : bool                   { get; }
    public abstract IsPrimitive           : bool                   { get; }
    public abstract IsSealed              : bool                   { get; }
    public abstract IsValueType           : bool                   { get; }
    public abstract IsUnsigned            : bool                   { get; }
    public virtual  IsVariantOption       : bool                   { get { false } }
    public virtual  IsVariant             : bool                   { get { false } }
    public virtual  IsTypeAlias           : bool                   { get { false } }
    public abstract LookupMemberAvailable : bool                   { get; }
    /// Underlying type of enum. This property appcable only to enum's TypeInfo.
    public abstract UnderlyingType        : ValueOption[ITypeInfo] { get; }


    // Methods
    public abstract CompareTo(other : ITypeInfo) : int;
    public abstract Equals(t : IMemberInfo) : bool;
    public abstract Equals(other : ITypeInfo) : bool;


    [Memoize]
    public GetConstructors(bindingFlags : NBindingFlags) : Seq[IMethodInfo]
    {
      GetMembers(bindingFlags).OfType.[IMethodInfo]().Where(m=>m.IsConstructor).NToArray()
    }

    public abstract GetDirectSuperTypes() : Seq[FixedType.Class];
    //public abstract GetExtensionPattern(name : string) : option[ExtensionPattern];

    [Memoize]
    public GetFields(bindingFlags : NBindingFlags) : Seq[IFieldInfo]
    {
      GetMembers(bindingFlags).OfType.[IFieldInfo]().NToArray()
    }

    [Memoize]
    public GetFields() : Seq[IFieldInfo]
    {
      GetMembers().OfType.[IFieldInfo]().NToArray()
    }

    public TryGetField(bindingFlags : NBindingFlags, predicate : IFieldInfo -> bool) : ValueOption[IFieldInfo]
    {
      def field = GetFields(bindingFlags).FindObject(predicate);
      if (field == null) ValueOption.None() else ValueOption.Some(field)
    }

    public TryGetField(predicate : IFieldInfo -> bool) : ValueOption[IFieldInfo]
    {
      def field = GetFields().FindObject(predicate);
      if (field == null) ValueOption.None() else ValueOption.Some(field)
    }

    public FindMethod(methodName : string, bindingFlags : NBindingFlags) : list[IMethodInfo]
    {
      mutable result = [];
      foreach(meth is IMethodInfo when meth.Name == methodName in GetMembers(bindingFlags))
        result ::= meth;
      result
    }

    public virtual GetNestedTypes() : Seq[ITypeInfo]
    {
      //foreach (t when t.DeclaringType == this : object in Assembly.AllTypes)
      //  yield t;
      GetMembers().OfType.[ITypeInfo]();
    }

    public abstract override GetHashCode() : int;
    public abstract GetMembers() : Seq[IMemberInfo];

    private static ConstrainMember(m : IMemberInfo, flags : NBindingFlags) : bool
    {
      def is_public = m.IsPublic;
      // if flag is Public then always return all public members
      flags == NBindingFlags.Public && is_public
      // if flag is NonPublic then always return all nonpublic members
        || flags == NBindingFlags.NonPublic && !is_public
      // masks
        ||  (!m.IsStatic || flags %&& NBindingFlags.Static)
          && (m.IsStatic || flags %&& NBindingFlags.Instance)
          && (!is_public || flags %&& NBindingFlags.Public)
          && (is_public  || flags %&& NBindingFlags.NonPublic)
    }

    public GetMembers(bindingFlags : NBindingFlags) : Seq[IMemberInfo]
    {
      def check (x : IMemberInfo)
      {
        if (bindingFlags %&& NBindingFlags.DeclaredOnly)
          this.Equals(x.DeclaringType) &&
          ConstrainMember(x, bindingFlags)
        else
          ConstrainMember(x, bindingFlags)
      }
      GetMembers().Where(check)
    }

    public override Equals(other : object) : bool
    {
      | other is ITypeInfo => Equals(other)
      | other is IMemberInfo => Equals(other)
      | _ => false
    }

    public override Type     : FixedType { get { SelfType : FixedType } }
    public abstract SelfType : FixedType.Class { get; }
    public abstract GetSuperTypes() : Seq[FixedType.Class]; // все базовые классы (рекурсивно)
    public abstract HasSuperType(typeInfo : ITypeInfo) : bool;
    public abstract IsDerivedFrom(fixedType : FixedType.Class) : bool;
    public abstract IsDerivedFrom(typeInfo : ITypeInfo) : bool;
    public abstract LookupMember(name : string, forCompletion : bool = false) : Seq[IMemberInfo];
    public abstract ConstructSubst(tc : ITypeInfo, typeParams : Seq[ITypeParameterInfo], args : Seq[NType], checkParams : bool) : Subst;
    public abstract FreshSubst() : Subst;
    public abstract MakeSubst(parms : Seq[NType]) : Subst;
    public abstract MakeUncheckedSubst(parms : Seq[NType]) : Subst;
    public abstract NestingSubst(parent : ITypeInfo) : Subst;

    public override ToString() : string
    {
      Name;
    }
  }
}
